//////////////////////////////////////////////////////////////////////////
//
// Water
// Water.cpp
// 
// Grant Johnson
// Qantm College
// 27/10/2010
//
//////////////////////////////////////////////////////////////////////////

#include "Water.h"
#include <d3dx9math.h>

#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include "Shader.h"
#include "ShaderMgr.h"
#include "RenderParams.h"
#include "Renderer.h"

Water::Water()
: m_pHeightData(NULL)
, m_vertCount(0)
, m_indexCount(0)
, m_primCount(0)
, m_pVertData(NULL)
, m_pIndexData(NULL)
, m_pTex(NULL)
{
	m_pTexFilename = new char[256];
	ZeroMemory(&m_mtl, sizeof(m_mtl));
	
	m_aabb.GetMin() = D3DXVECTOR3(0, 0, 0);
	m_aabb.GetMax() = D3DXVECTOR3((float)(m_gridVertX-1)*m_terrainWidth, (float)(m_gridVertX-1)*m_terrainWidth, (float)(m_gridVertZ-1)*m_terrainWidth);
}
Water::~Water()
{
	//Clean up data
	if(m_pHeightData)
	{
		delete m_pHeightData;
		m_pHeightData = NULL;
	}
	if(m_pVertData)
	{
		delete m_pVertData;
		m_pVertData = NULL;
	}
	if(m_pIndexData)
	{
		delete m_pIndexData;
		m_pIndexData = NULL;
	}
	if(m_pTex)
	{
		m_pTex->Release();
		m_pTex = NULL;
	}
	if(m_pTexFilename)
	{
		delete m_pTexFilename;
		m_pTexFilename = NULL;
	}
}
bool Water::CreateObject(IDirect3DDevice9* a_pDevice, char* a_pTexFileName)
{
	//Ensure there was a valid device passed in
	if(!a_pDevice)
	{
		return false;
	}

	m_gridVertX = 50;
	m_gridVertZ = 50;
	m_terrainWidth = 2;

	//Generate based on grid size
	m_vertCount = m_gridVertX*m_gridVertZ;
	m_indexCount = ((m_gridVertX-1) * (m_gridVertZ-1))*6;
	m_primCount = m_indexCount/3;

	//Copy of the texture filename
	sprintf_s(m_pTexFilename, 256, "%s", a_pTexFileName);

	D3DXCreateTextureFromFile(a_pDevice, m_pTexFilename, &m_pTex);

	//Setup the material
	m_mtl.Ambient = D3DXCOLOR(0.00f, 0.05f, 0.00f, 1.0f);
	m_mtl.Diffuse = D3DXCOLOR(0.0f, 0.0f, 1.0f, 1.0f);
	//m_mtl.Specular = D3DXCOLOR(0.0f, 0.1f, 0.0f, 1.0f);
	m_mtl.Power = 0.1f;

	//Create vertex buffer
	HRESULT result  = a_pDevice->CreateVertexBuffer(sizeof(VertexData) * m_vertCount, 0, VERTEX_FORMAT_FVF, D3DPOOL_DEFAULT, &m_pVertexBuffer, NULL);
	if(result != D3D_OK)
	{
		return false;
	}

	//Create index buffer
	result = a_pDevice->CreateIndexBuffer(sizeof(short) * m_indexCount, 0, D3DFMT_INDEX16, D3DPOOL_DEFAULT, &m_pIndexBuffer, NULL);
	if(result != D3D_OK)
	{
		//If couldnt make the index buffer, release the vertex data just allocated
		m_pVertexBuffer->Release();
		m_pVertexBuffer = NULL;
		return false;
	}

	//Calculate the normals on the terrain for lighting
	CalculateNormals();

	//Create terrain index data
	m_pIndexData = new unsigned short[m_indexCount];
	long curIndex = 0;

	//Minus one to loop through all the quads, not all the verts
	for(long posZ = 0; posZ < m_gridVertZ-1; ++posZ)
	{
		for(long posX = 0; posX < m_gridVertX-1; ++posX)
		{
			//Setup indices
			//top left
			m_pIndexData[curIndex] = (unsigned short)(posZ*m_gridVertX)+(unsigned short)posX;
			//bottom right
			m_pIndexData[curIndex+1] = (unsigned short)((posZ+1)*m_gridVertX)+(unsigned short)(posX+1);
			//top right
			m_pIndexData[curIndex+2] = (unsigned short)(posZ*m_gridVertX)+(unsigned short)(posX+1);
			//top left 
			m_pIndexData[curIndex+3] = (unsigned short)(posZ*m_gridVertX)+(unsigned short)posX;
			//bottom left
			m_pIndexData[curIndex+4] = (unsigned short)((posZ+1)*m_gridVertX)+(unsigned short)posX;
			//bottom right
			m_pIndexData[curIndex+5] = (unsigned short)((posZ+1)*m_gridVertX)+(unsigned short)(posX+1);

			curIndex+=6;
		}
	}

	assert(curIndex == m_indexCount);

	void* pLockedData = NULL;
	m_pVertexBuffer->Lock(0, sizeof(VertexData) * m_vertCount, &pLockedData, 0);
	memcpy(pLockedData, m_pVertData, sizeof(VertexData) * m_vertCount);
	m_pVertexBuffer->Unlock();
	pLockedData = NULL;

	m_pIndexBuffer->Lock(0, sizeof(short) * m_indexCount, &pLockedData, 0);
	memcpy(pLockedData, m_pIndexData, sizeof(short) * m_indexCount);
	m_pIndexBuffer->Unlock();
	pLockedData = NULL;

	return true;
}
void Water::Render(float a_dt, IDirect3DDevice9 *a_pDevice, const RenderParams& a_renderParams)
{
	if(a_pDevice && m_pVertexBuffer && m_pIndexBuffer)
	{
		a_pDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP);
		a_pDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP);

		//set world matrix
		a_pDevice->SetTransform(D3DTS_WORLD, &m_worldMatrix);

		//tell directx about vertex data
		a_pDevice->SetStreamSource(0, m_pVertexBuffer, 0, sizeof(VertexData));
		a_pDevice->SetFVF(VERTEX_FORMAT_FVF);

		a_pDevice->SetIndices(m_pIndexBuffer);
		
		const char* SHADER_NAME = "AdvancedShader";
		Shader* pShader = ShaderMgr::GetInstance().Find(SHADER_NAME);
		if(pShader)
		{
			ID3DXEffect* pEffect = pShader->GetEffect();
			if(pEffect)
			{
				pEffect->SetTechnique("AdvRenderDiff1Spec1Dir1Omni1NoColBlend");

				//Set material global variables
				pEffect->SetValue("g_materialAmbientColor", &D3DXCOLOR(m_mtl.Ambient), sizeof(D3DXCOLOR));
				pEffect->SetValue("g_materialDiffuseColor", &D3DXCOLOR(m_mtl.Diffuse), sizeof(D3DXCOLOR));
				pEffect->SetValue("g_materialSpecularColor", &D3DXCOLOR(m_mtl.Specular), sizeof(D3DXCOLOR));
				pEffect->SetValue("g_materialEmissiveColor", &D3DXCOLOR(m_mtl.Emissive), sizeof(D3DXCOLOR));
				pEffect->SetFloat("g_materialSpecularPower", m_mtl.Power);
				pEffect->SetFloat("g_materialSpecularIntensity", 2.0f);

				//Set texture global variable
				pEffect->SetTexture("g_diffuseTexture", m_pTex);
				pEffect->SetTexture("g_specularTexture", m_pTex);

				pEffect->SetValue("g_fogColor", &D3DXCOLOR(0.3f, 0.35f, 0.4f, 1.0f), sizeof(D3DXCOLOR));

				pEffect->SetFloat("g_fogDistStart", 10);
				pEffect->SetFloat("g_fogDistEnd", 500);
				pEffect->SetValue("g_cameraPos", Renderer::GetInstance().GetActiveCamera()->GetEyePos(), sizeof(D3DXVECTOR3));

				//Set up lighting global variables
				for(int lightId = 0; lightId < a_renderParams.GetLightCount(); ++lightId)
				{
					if(a_renderParams.GetLight(lightId)->Type == D3DLIGHT_DIRECTIONAL)
					{
						//Set lighting parameters
						//Note: Light direction is negated to optimise the dot product in 
						//shader calculations
						pEffect->SetValue("g_directionalDir", &(D3DXVECTOR3(a_renderParams.GetLight(lightId)->Direction) * -1.0f), sizeof(D3DXVECTOR3));
						pEffect->SetValue("g_directionalDiffuse", &D3DXCOLOR(a_renderParams.GetLight(lightId)->Diffuse), sizeof(D3DXCOLOR));
						pEffect->SetValue("g_directionalSpecular", &D3DXCOLOR(a_renderParams.GetLight(lightId)->Specular), sizeof(D3DXCOLOR));
					}
					else if(a_renderParams.GetLight(lightId)->Type == D3DLIGHT_POINT)
					{
						pEffect->SetValue("g_omniPos", &D3DXVECTOR3(a_renderParams.GetLight(lightId)->Position), sizeof(D3DXVECTOR3));
						pEffect->SetFloat("g_omniRange", a_renderParams.GetLight(lightId)->Range);
						pEffect->SetValue("g_omniDiffuse", &D3DXCOLOR(a_renderParams.GetLight(lightId)->Diffuse), sizeof(D3DXCOLOR));
						pEffect->SetValue("g_omniSpecular", &D3DXCOLOR(a_renderParams.GetLight(lightId)->Specular), sizeof(D3DXCOLOR));
					}
				}
				pEffect->SetValue("g_totalAmbient", &D3DXCOLOR(a_renderParams.GetGlobalAmbient()), sizeof(D3DXCOLOR));
				
				//Set matrix global variables
				//Calculate a combined world*view*proj matrix for the vertex shader
				D3DXMATRIX worldViewProj = m_worldMatrix*a_renderParams.GetViewMatrix()*a_renderParams.GetProjMatrix();
				pEffect->SetMatrix("g_worldViewProj", &worldViewProj);
				pEffect->SetMatrix("g_worldMat", &m_worldMatrix);

				//Render with shader
				UINT passCount = 0;
				if(pEffect->Begin(&passCount, 0) == D3D_OK)
				{
					for(UINT passIndex = 0; passIndex < passCount; ++passIndex)
					{		
						if(pEffect->BeginPass(passIndex) == D3D_OK)
						{
							//Draw the terrain
							a_pDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, m_vertCount, 0, m_primCount);
							pEffect->EndPass();
						}
					}
					pEffect->End();
				}
			}
		}
	}
}
void Water::CalculateNormals()
{
	for(long posZ = 0; posZ < m_gridVertZ; ++posZ)
	{
		for(long posX = 0; posX < m_gridVertX; ++posX)
		{
			const long vertIndex = (posZ * m_gridVertX)+posX;
			D3DXVECTOR3 up,down,left,right;

			//if not top row
			if(posZ < m_gridVertZ - 1)
			{
				up = m_pVertData[((posZ+1)*m_gridVertX)+posX].pos;
				up -= m_pVertData[vertIndex].pos;
				D3DXVec3Normalize(&up, &up);
			}
			else
			{
				//@SCOTTS HACK 
				m_pVertData[vertIndex].normal = D3DXVECTOR3(0,1,0);
				continue;
			}

			//if not bottom row
			if(posZ != 0)
			{
				down = m_pVertData[((posZ-1)*m_gridVertX)+posX].pos;
				down -= m_pVertData[vertIndex].pos;
				D3DXVec3Normalize(&down, &down);
			}
			else
			{
				//@SCOTTS HACK 
				m_pVertData[vertIndex].normal = D3DXVECTOR3(0,1,0);
				continue;
			}

			//if not right most column
			if(posX < m_gridVertX - 1)
			{
				right = m_pVertData[(posZ*m_gridVertX)+(posX+1)].pos;
				right -= m_pVertData[vertIndex].pos;
				D3DXVec3Normalize(&right, &right);
			}
			else
			{
				//@SCOTTS HACK 
				m_pVertData[vertIndex].normal = D3DXVECTOR3(0,1,0);
				continue;
			}

			//if not left most column
			if(posX != 0)
			{
				left = m_pVertData[(posZ*m_gridVertX)+(posX-1)].pos;
				left -= m_pVertData[vertIndex].pos;
				D3DXVec3Normalize(&left, &left);
			}
			else
			{
				//@SCOTTS HACK 
				m_pVertData[vertIndex].normal = D3DXVECTOR3(0,1,0);
				continue;
			}

			//cross products
			//up and left
			D3DXVECTOR3 temp1;
			D3DXVECTOR3 temp2;
			D3DXVec3Cross(&temp1, &down, &left);

			//cross products
			//down and right
			D3DXVec3Cross(&temp2, &up, &right);

			temp1 += temp2;

			//Normalize the new vector to give a unit length directional vector
			D3DXVec3Normalize(&temp1, &temp1);
			//Apply the normal
			m_pVertData[vertIndex].normal = temp1;
		}
	}
}

void Water::SmoothTerrain()
{
	for(long posZ = 0; posZ < m_gridVertZ; ++posZ)
	{
		for(long posX = 0; posX < m_gridVertX; ++posX)
		{
			const long vertIndex = (posZ * m_gridVertX)+posX;
			float up,down,left,right;
			up=down=left=right = 0;
			int average = 0;

			//if not top row
			if(posZ != 0)
			{
				up = m_pHeightData[((posZ-1)*m_gridVertX)+posX];
				++average;
			}
			//if not bottom row
			if(posZ != m_gridVertZ-1)
			{
				down = m_pHeightData[((posZ+1)*m_gridVertX)+posX];
				++average;
			}
			//if not right most column
			if(vertIndex != ((posZ*m_gridVertX)+(m_gridVertX-1)))
			{
				right = m_pHeightData[(posZ*m_gridVertX)+(posX+1)];
				++average;
			}
			//if not left most column
			if(vertIndex != posZ*m_gridVertX)
			{
				left = m_pHeightData[posZ*m_gridVertX+(posX-1)];
				++average;
			}
			//Get an average of the surrounding verts and apply to the height data
			float avg = ((up+down)+(left+right))/average;
			m_pHeightData[vertIndex] = avg;
		}
	}
}

void Water::SetupVertices(D3DXVECTOR3* a_pVertsArray)
{
	m_pVertData = new VertexData[4244];//change from hardcoded to size of the array
	int index = 0;
	while(index <= 4244)
	{
		VertexData& vert = m_pVertData[index];
		vert.pos = a_pVertsArray[index];
		vert.u = 0;
		vert.v = 0;
		
		++index;
	}
}
void Water::SetupIndices()
{	
	//Minus one to loop through all the quads, not all the verts
	/*for(long posZ = 0; posZ < m_gridVertZ-1; ++posZ)
	{
		for(long posX = 0; posX < m_gridVertX-1; ++posX)
		{
			//Setup indices
			//top left
			m_pIndexData[curIndex] = (unsigned short)(posZ*m_gridVertX)+(unsigned short)posX;
			//bottom right
			m_pIndexData[curIndex+1] = (unsigned short)((posZ+1)*m_gridVertX)+(unsigned short)(posX+1);
			//top right
			m_pIndexData[curIndex+2] = (unsigned short)(posZ*m_gridVertX)+(unsigned short)(posX+1);
			//top left 
			m_pIndexData[curIndex+3] = (unsigned short)(posZ*m_gridVertX)+(unsigned short)posX;
			//bottom left
			m_pIndexData[curIndex+4] = (unsigned short)((posZ+1)*m_gridVertX)+(unsigned short)posX;
			//bottom right
			m_pIndexData[curIndex+5] = (unsigned short)((posZ+1)*m_gridVertX)+(unsigned short)(posX+1);

			curIndex+=6;
		}
	}*/
}
